package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yanbing
 * @version 1.0
 * @date 2021/9/4 21:12
 */
public class AnnotationConfigApplicationContext implements ApplicationContext {

    //存储bean定义信息，bean定义信息用途是生成bean对象
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionConcurrentHashMap = new ConcurrentHashMap<>();
    //已经初始化的单实例bean对象，全局唯一
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    //所以的BeanPostProcessor的子类，在spring源码BeanPostProcessor是非常重要类，aop和类的扩展，@Value都是通过它类实现的
    private List<BeanPostProcessor> beanPostProcessorList = new LinkedList<>();


    /**
     * spring 启动流程
     *
     * @param componentClasses
     */
    public AnnotationConfigApplicationContext(Class<?> componentClasses) {

        //扫描整个包， 得到BeanDefinition
        scan(componentClasses);

        //实例话非懒加载单列bean
        //  1 实例化
        //  2 属性赋值
        //  3 Aare接口回调  @PostConstruct InitializingBean
        //  4 初始化
        //  5 添加到单实例缓存 singletonObjects
        finishSingletonBeanInitialization();

    }

    private void finishSingletonBeanInitialization() {
        for (String beanName : beanDefinitionConcurrentHashMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionConcurrentHashMap.get(beanName);
            if (beanDefinition.getScope().equals(ScopeEnum.singleton)) {
                Object bean = doCreateBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    private Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        Class beanClass = beanDefinition.getBeanClass();
        try {
            //构造函数实例化，静态代码块和代码块就是在这里执行的
            Object instance = beanClass.getDeclaredConstructor().newInstance();
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                //依赖注入
                if (field.isAnnotationPresent(Autowired.class)) {
                    //真实spring源码通过属性的类型获取对象的，属性的名称可以随意命名
                    String filedName = field.getName();
                    Object bean = getBean(filedName);
                    field.setAccessible(true);
                    field.set(instance, bean);
                }
            }

            //Aware回调
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            //afterPropertiesSet回调
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            //beanPostProcessor 初始化前处理
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }
            return instance;

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void scan(Class<?> componentClasses) {
        //获得需要扫描路径
        ComponentScan componentClassesAnnotation = componentClasses.getAnnotation(ComponentScan.class);
        String packagePath = componentClassesAnnotation.value();
        if (packagePath.equals("")) {
            throw new SpringException("packagePath cannot null  ");
        }

        //获得AppClassLoader加载后的class（经过类的）
        List<Class> beanList = getBeanClasses(packagePath);
        //遍历beanClasses得到BeanDefinition
        for (Class clazz : beanList) {
            //只有类上加了注解Component才会生成BeanDefinition
            if (clazz.isAnnotationPresent(Component.class)) {
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setBeanClass(clazz);

                Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
                String beanName = componentAnnotation.value();
                if (beanName.equals("")) {
                    beanName = Introspector.decapitalize(clazz.getSimpleName());
                }

                //判断类是否为BeanPostProcessor父类,，然后添加到BeanPostProcessorList
                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    try {
                        BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                        beanPostProcessorList.add(beanPostProcessor);
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }

                //解析scope
                if (clazz.isAnnotationPresent(Scope.class)) {
                    Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                    String scopeValue = scope.value();
                    if (ScopeEnum.singleton.name().equals(scopeValue)) {
                        beanDefinition.setScope(ScopeEnum.singleton);
                    } else {
                        beanDefinition.setScope(ScopeEnum.prototype);
                    }
                } else {
                    beanDefinition.setScope(ScopeEnum.prototype);
                }
                System.out.println("beanDefinition :" + beanDefinition.toString());
                beanDefinitionConcurrentHashMap.put(beanName, beanDefinition);
            }
        }
    }

    private List<Class> getBeanClasses(String packagePath) {
        /**
         * 获得AppClassLoader加载器
         * 加载过程经过：加载、验证、准备、解析
         * 加载：在硬盘中查找并通过IO读入字节码文件至JVM虚拟机方法区，同时在堆中创建class对象
         * 验证：校验字节码文件的正确性
         * 准备：为类的静态变量分配内存，并将期初始化默认值。此阶段仅为静态变量（static修饰的字段变量）分配内存
         * （例如static int=5 ，这里初始化值为0，5的赋值是在初始化时赋值）；对于final static在编译的时候就已经分配了，
         * 解析：把类中的符号引用转换为直接引用
         *
         */
        List<Class> beanClasses = new ArrayList<>();
        ClassLoader classLoader = AnnotationConfigApplicationContext.class.getClassLoader();
        packagePath = packagePath.replace(".", "/");
        URL resource = classLoader.getResource(packagePath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileName = f.getAbsolutePath();
                if (fileName.endsWith(".class")) {
                    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                    className = className.replace("\\", ".");
                    try {
                        Class<?> clazz = classLoader.loadClass(className);
                        beanClasses.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
            for (Class beanClass : beanClasses) {
                System.out.println("scan class :" + beanClass.toString());
            }
        } else {
            throw new SpringException("packagePath cannot directory ");
        }
        return beanClasses;
    }

    @Override
    public Object getBean(String beanName, Object... args) {
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else {
            BeanDefinition beanDefinition = beanDefinitionConcurrentHashMap.get(beanName);
            return doCreateBean(beanName, beanDefinition);
        }
    }
}
